package it.unibo.refolding.pico;

import it.unibo.refolding.common.CommonThings;
import it.unibo.refolding.pico.PicoDataAnalyser.ContactPointAndSlope;
import it.unibo.refolding.pico.PicoDataAnalyser.ContactPointsAndSlope;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.lang3.StringUtils;

import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;

public class ContactPointAndSlopeHelper {

	private static final Logger logger = Logger.getLogger(ContactPointAndSlopeHelper.class.getSimpleName());

	public static void writeToFile(ContactPointAndSlope cps, Path file) throws IOException {
		Files.write(file, (StringUtils.join(DoubleArrayList.wrap(
				new double[] { cps.zLsb, cps.defLsb, cps.slopeZLsbDefLsb }).toArray(), ", ")
				+ "\n").getBytes(Charsets.ISO_8859_1));
	}

	private static final Pattern splitPattern = Pattern.compile(",\\s*");

	public static ContactPointAndSlope extractContactPointAndSlope(Path cpsFile) throws IOException {
		/*
		 * It's usually between 62 and 64 bytes long, but Files.readAllBytes
		 * does not accept a size estimate.
		 */
		String s = new String(Files.readAllBytes(cpsFile), Charsets.ISO_8859_1);
		String[] split = splitPattern.split(s.trim());
		Preconditions.checkState(split.length == 3);
		return new ContactPointAndSlope(Double.parseDouble(split[0]), Double.parseDouble(split[1]),
				Double.parseDouble(split[2]));
	}

	public static ContactPointAndSlope computeContactPointAndSlope(PlainPicoData fetch) {
		return PicoDataAnalyser.findApproachContactPointAndSlope(fetch.sharedApproach(), Double.NaN);
	}

	public static Option createContactPointAndSlopeDirOption() {
		Option contactPointAndSlopeDirOption = new Option("contactPointAndSlopeDir", true,
				"directory with .cps files containing retraction contact point and baseline slope");
		contactPointAndSlopeDirOption.setRequired(true);
		contactPointAndSlopeDirOption.setType(String.class);
		return contactPointAndSlopeDirOption;
	}

	public static Path contactPointAndSlopeFile(Path dir, String filename) {
		return dir == null ? null : dir.resolve(filename + ".cps");
	}

	public static Path createCpsDirIfNeeded(Option cpsDirOption, CommandLine line) {
		String cpsDir = line.getOptionValue(cpsDirOption.getOpt());
		return CommonThings.createDirIfNeeded(cpsDir);
	}

	/**
	 * The slope from the file wins over the argument slope.
	 */
	public static ContactPointsAndSlope readOrComputeAndWriteContactPointAndSlopeUsingApproachBaseline(
		PlainPicoData fetch, String filename, double slope, Path contactPointAndSlopeDir)
		throws IOException {
		final ContactPointsAndSlope contactPointsAndSlope;
		// Works also when contactPointAndSlopeDir is null.
    Path contactPointAndSlopeFile = contactPointAndSlopeDir == null ? null
      : contactPointAndSlopeFile(contactPointAndSlopeDir, filename);
		if (contactPointAndSlopeDir == null || !Files.isRegularFile(contactPointAndSlopeFile)) {
			contactPointsAndSlope = PicoDataAnalyser
				.computeContactPointsAndSlopeUsingApproachBaseline(fetch, slope);
			if (contactPointAndSlopeDir != null) {
				writeToFile(
					contactPointsAndSlope.retractionContactPointAndSlope(),
					contactPointAndSlopeFile);
			}
		} else {
			ContactPointAndSlope retractionContactPointAndSlope = extractContactPointAndSlope(
				contactPointAndSlopeFile);
			contactPointsAndSlope = bothContactPointsAndSlope(
				fetch, retractionContactPointAndSlope);
		}
		return contactPointsAndSlope;
	}

	/**
	 * The slope from the file wins over the argument slope.
	 */
	public static ContactPointsAndSlope readOrComputeAndWriteContactPointAndSlopeSharingSlopeBetweenApproachAndRetraction(
		PlainPicoData fetch, double approachSkipDistanceM, double slope, String filename, Path contactPointAndSlopeDir)
		throws IOException {
		final ContactPointsAndSlope contactPointsAndSlope;
		// Works also when contactPointAndSlopeDir is null.
		Path contactPointAndSlopeFile = contactPointAndSlopeFile(
			contactPointAndSlopeDir, filename);
		if (contactPointAndSlopeDir == null || !Files.isRegularFile(contactPointAndSlopeFile)) {
			ContactPointAndSlope approachContactPointAndSlope = PicoDataAnalyser
				.findApproachContactPointAndSlope(
					fetch.sharedApproach().subRange(
						0,
						1 + PicoDataAnalyser.offsetOfStartingContiguousRegionNotBefore(
							fetch.sharedApproach().z, approachSkipDistanceM
								/ fetch.zLsbToMetresFactor)), slope);
			ZLsbDeflectionLsbPoint retractionContactPoint = PicoDataAnalyser.findRetractionContactPointGivenSlope(
				fetch.sharedRetraction(), approachContactPointAndSlope.slopeZLsbDefLsb);
			contactPointsAndSlope = new ContactPointsAndSlope(
				approachContactPointAndSlope.contactPoint(), retractionContactPoint, approachContactPointAndSlope.slopeZLsbDefLsb);
			if (contactPointAndSlopeDir != null) {
				writeToFile(
					contactPointsAndSlope.retractionContactPointAndSlope(),
					contactPointAndSlopeFile);
			}
		} else {
			ContactPointAndSlope retractionContactPointAndSlope = extractContactPointAndSlope(
				contactPointAndSlopeFile);
			contactPointsAndSlope = bothContactPointsAndSlope(
				fetch, retractionContactPointAndSlope);
		}
		return contactPointsAndSlope;
	}

	public static ContactPointAndSlope readOrWriteContactPointAndSlope(
		ContactPointAndSlope cps, String filename, Path contactPointAndSlopeDir)
		throws IOException {
		// Works also when contactPointAndSlopeDir is null.
		Path contactPointAndSlopeFile = contactPointAndSlopeFile(
			contactPointAndSlopeDir, filename);
		return readOrWriteContactPointAndSlope(cps, contactPointAndSlopeFile);
	}

  public static ContactPointAndSlope readOrWriteContactPointAndSlope(
    ContactPointAndSlope cps, Path contactPointAndSlopeFile) throws IOException {
    Preconditions.checkNotNull(cps);
		final ContactPointAndSlope retractionCps;
		if (contactPointAndSlopeFile == null || !Files.isRegularFile(contactPointAndSlopeFile)) {
			if (contactPointAndSlopeFile != null) {
				writeToFile(cps, contactPointAndSlopeFile);
			}
			retractionCps = cps;
		} else {
			ContactPointAndSlope retractionContactPointAndSlope = extractContactPointAndSlope(
				contactPointAndSlopeFile);
			retractionCps = retractionContactPointAndSlope;
		}
		return retractionCps;
  }
	
	public static ContactPointsAndSlope bothContactPointsAndSlope(PlainPicoData fetch,
			ContactPointAndSlope retractionContactPointAndSlope) {
		ContactPointAndSlope approachCps = PicoDataAnalyser
				.findApproachContactPointAndSlope(fetch.sharedApproach(),
						retractionContactPointAndSlope.slopeZLsbDefLsb);
		Preconditions.checkState(approachCps.slopeZLsbDefLsb == retractionContactPointAndSlope.slopeZLsbDefLsb);
		return new ContactPointsAndSlope(approachCps.contactPoint(), retractionContactPointAndSlope
				.contactPoint(), retractionContactPointAndSlope.slopeZLsbDefLsb);
	}

	public static ContactPointsAndSlope readOrComputeAndWriteContactPointAndSlopeOrNullUsingApproachBaseline(
		PlainPicoData fetch, double slope, String filename, Path contactPointAndSlopeDir) throws IOException {
		try {
			return readOrComputeAndWriteContactPointAndSlopeUsingApproachBaseline(
				fetch, filename, slope, contactPointAndSlopeDir);
		} catch (IllegalArgumentException ex) {
			logger.log(Level.WARNING, "Could not analyse {0} because of {1}.", new Object[] {
				filename, ex });
			return null;
		} catch (IllegalStateException ex) {
			logger.log(Level.WARNING, "Could not analyse {0} because of {1}.", new Object[] {
				filename, ex });
			return null;
		}
	}
}
